每年新 iPhone 上市後,iFixit 網站就會發表新手機 teardown review,將手機拆開,一步一步的分解裡面的零件,最後對機器的維修程度打個分數。我們可以從各個零件去判斷它們各是由哪些廠商供應給蘋果,像是記憶體是三星,CPU 是台積電,螢幕是LG,相機鏡頭是大立光等等,並非所有零件都由蘋果自己製作。這樣的好處是讓蘋果能專心在自己擅長的領域,每年推出新手機。對我們消費者來說,好處是手機故障了只要更換壞掉的零件就可以。
這就是「模組化」,將一個強大的物體拆分成許多小型且結構良好的模組,一個模組只做一件簡單的事,透過公開介面向外界溝通,當多個模組被組織結合在一起,就成了一個功能強大的集合體。
當應用程式功能愈強,程式碼會愈多,就會面臨程式碼管理的問題,程式設計師也構思了各種程式碼模組化的方法。
早期當我們還是用<script>
載入 JS 檔案的年代,最直覺的管理方法是將不同功能的程式碼寫在不同檔案裡。
// a.js
var foo = "foo";
var baz = "foo-baz";
// b.js
var bar = "bar";
var baz = "bar-baz";
// index.html
<html>
<head>
<title>...</title>
<script src="a.js"></script>
<script src="b.js"></script>
<script>
console.log(foo); // "foo", because window.foo = "foo"
console.log(bar); // "bar", because window.bar = "bar"
console.log(baz); // "bar-baz" 命名衝突
</script>
<head>
<body>
...
</body>
</html>
前面我們討論過 JavaScript 一直以來只有全域和函式這二種變數範圍而已,也就是說在 JavaScript 檔案裡定義的變數,只要不是在函式內,都是全域變數。因為都會定義成window
物件的屬性,window
是全域物件,在網頁生命週期之內都不會消失。
如此一來會發生命名衝突的問題,自己開發的小程式還能控制,一旦和別人共同協作,或是引用第三方程式碼,一定會碰到這個問題。另外也會有程式依賴的問題,被依賴的檔案需要優先載入,不巧哪天某個新手手滑刪掉被依賴的檔案,或者變更了檔案載入的順序,應用程式便會發生錯誤,甚至整個死當。